Skip to content

fix(ui): extract theme-flash inline script to /theme-flash.js for strict CSP#104

Merged
aksOps merged 1 commit intomainfrom
fix/csp-extract-theme-flash
May 4, 2026
Merged

fix(ui): extract theme-flash inline script to /theme-flash.js for strict CSP#104
aksOps merged 1 commit intomainfrom
fix/csp-extract-theme-flash

Conversation

@aksOps
Copy link
Copy Markdown
Contributor

@aksOps aksOps commented May 4, 2026

Summary

The strict CSP shipped from internal/api/security_headers.go uses:

script-src 'self' 'wasm-unsafe-eval'

— no 'unsafe-inline'. The theme-flash FOUC guard in ui/index.html was an inline <script> block, so every page load logged a CSP violation:

Refused to execute inline script because it violates the following Content Security Policy directive: script-src 'self' 'wasm-unsafe-eval'

The script never ran, which meant a brief flash-of-wrong-theme on first paint when the user's persisted choice differed from the OS preference.

Fix

Move the script body verbatim into ui/public/theme-flash.js (Vite copies public/ to /dist root at build time) and reference it from index.html via:

<script src="/theme-flash.js"></script>

CSP 'self' allows it without an 'unsafe-inline' exception. The script body is unchanged, so behavior is identical when it runs.

The ui/dist/index.html placeholder (committed for //go:embed ui/dist) is updated to match the new structure.

Trade-off

One extra HTTP/2 request before paint for /theme-flash.js (1.2 KiB). Well below the perceptible-jank threshold and worth keeping the strict CSP intact. Could be eliminated with a CSP script-src SHA-256 hash, but that introduces hash-sync coupling between the Go server and the JS file — not worth it for a 1.2 KiB script that rarely changes.

Test plan

  • npm --prefix ui run build produces dist/theme-flash.js (1.2 KiB)
  • dist/index.html no longer contains the inline <script> block
  • dist/index.html contains <script src=\"/theme-flash.js\">
  • go build -tags sqlite_fts5 clean
  • Local smoke: docsiq serve --port 37794 returns HTTP 200 on / AND /theme-flash.js (200, 1227 bytes)
  • CSP header on / unchanged
  • Post-merge browser smoke: dev tools console clean of inline-script CSP violations
  • Post-merge: dark-mode-persisted user reloads in fresh tab, no flash-of-light-mode

Compatibility

Drop-in. Behavior of the script is identical; only the delivery mechanism changed.

🤖 Generated with Claude Code

…ict CSP

The strict CSP shipped from internal/api/security_headers.go uses
script-src 'self' 'wasm-unsafe-eval' — no 'unsafe-inline'. The
theme-flash FOUC guard in index.html was an inline <script>, so every
page load logged a CSP violation:

  Refused to execute inline script because it violates the following
  Content Security Policy directive: script-src 'self' 'wasm-unsafe-eval'

The script never ran, which meant a brief flash-of-wrong-theme on
first paint in dark mode (and vice-versa).

Move the script verbatim into ui/public/theme-flash.js (Vite copies
public/ to /dist root at build time) and reference it from index.html
via <script src="/theme-flash.js">. CSP 'self' allows it without an
inline-script exception. The script body is unchanged so behaviour is
identical when it runs.

Trade-off: one extra HTTP/2 request before paint. The file is 1.2 KiB
so this is well below the perceptible-jank threshold and worth it for
keeping the strict CSP intact.
@aksOps aksOps enabled auto-merge (squash) May 4, 2026 07:35
@aksOps aksOps merged commit 4283d09 into main May 4, 2026
17 of 18 checks passed
@aksOps aksOps deleted the fix/csp-extract-theme-flash branch May 4, 2026 07:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant